--[[

    Filename:	auxmqtt.lua

    Programmer:	aesiot

    Remark:

--]]

local MQTT   = require( "MQTT"   )
local auxlib = require( "auxlib" )

local auxmqtt = { _VERSION = "1.0" }

--------------------------------------------------------------------------------

function auxmqtt.new( host, port, hostname, cert, key, ca )

	local mqtt = nil

	if hostname then

		mqtt = auxlib.assert( MQTT.new_tls( host, port, hostname, cert, key, ca ) )
	else

		mqtt = auxlib.assert( MQTT.new_tcp() )

		auxlib.assert( auxlib.select( 1, 2, mqtt:connect( host, port ) ) )
	end

	local result = {}

	local metatable = { Ident = 0, Object = mqtt, Registry = {}, Strainer = {},

		__index = {},

		__metatable = "cannot get a protected metatable"
	}

	function mqtt.on_close()

		auxlib.pcall( result.on_close, result )
	end

	function mqtt.on_connected()

		auxlib.pcall( result.on_connected, result )
	end

	function mqtt.on_destroy()

		auxlib.pcall( result.on_destroy, result )

		result = nil
	end

	function mqtt.on_disconnect()

		auxlib.pcall( result.on_disconnect, result )
	end

	function mqtt.on_message( topic, payload )

		local proc = metatable.Registry[ topic ]

		if proc then

			auxlib.pcall( proc, result, payload, topic )
		else

			for k, v in pairs( metatable.Strainer ) do

				if string.match( topic, k ) then

					auxlib.pcall( v, result, payload, topic )

					return
				end
			end

			auxlib.pcall( result.on_message, result, topic, payload )
		end
	end

	function mqtt.on_pong()

		auxlib.pcall( result.on_pong, result )
	end

	function mqtt.on_ready()

		auxlib.pcall( result.on_ready, result )
	end

	function mqtt.on_reconnect()

		auxlib.pcall( result.on_reconnect, result )

		metatable.Object.on_close      = nil
		metatable.Object.on_connected  = nil
		metatable.Object.on_disconnect = nil
		metatable.Object.on_message    = nil
		metatable.Object.on_ready      = nil
		metatable.Object.on_reconnect  = nil
		metatable.Object.on_session    = nil

		metatable.Object = nil

		metatable.Registry = nil
		metatable.Strainer = nil

		metatable = nil
	end

	function mqtt.on_session( ident, errno )

		local proc = metatable.Registry[ ident ]

		metatable.Registry[ ident ] = nil

		if proc then

			auxlib.pcall( proc, result, errno )
		else

			auxlib.pcall( result.on_session, result, ident, errno )
		end
	end

	function metatable:gen_ident()

		local ident = ( self.Ident % 65535 ) + 1

		self.Ident = ident

		return ident
	end

	function metatable.__index:bind( topic, proc )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_string( topic ), 2, "(string expected)" )

		metatable.Registry[ topic ] = proc

		return self
	end

	function metatable.__index:bind_filter( pattern, proc )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_string( pattern ), 2, "(string expected)" )

		metatable.Strainer[ pattern ] = proc

		return self
	end

	function metatable.__index:close()

		auxlib.assert( self == result )

		auxlib.assert( auxlib.select( 1, 2, metatable.Object:close() ) )

		return self
	end

	function metatable.__index:login( keep_alive, client_id, username, password, will_topic, will_message )

		auxlib.assert( self == result )

		auxlib.assert( auxlib.select( 1, 2, metatable.Object:login( keep_alive, client_id, username, password, will_topic, will_message ) ) )

		return self
	end

	function metatable.__index:logout()

		auxlib.assert( self == result )

		auxlib.assert( auxlib.select( 1, 2, metatable.Object:logout() ) )

		return self
	end

	function metatable.__index:publish( topic, message, qos, retain, ackfn )

		auxlib.assert( self == result )

		local ident = metatable:gen_ident()

		auxlib.assert( auxlib.select( 1, 2, metatable.Object:publish( ident, topic, message, qos, retain ) ) )

		if MQTT.QoS1 == qos or MQTT.QoS2 == qos then

			metatable.Registry[ ident ] = ackfn
		end

		return self
	end

	function metatable.__index:subscribe( topic, qos, ackfn )

		auxlib.assert( self == result )

		local ident = metatable:gen_ident()

		auxlib.assert( auxlib.select( 1, 2, metatable.Object:subscribe( ident, topic, qos ) ) )

		metatable.Registry[ ident ] = ackfn

		return self
	end

	function metatable.__index:unsubscribe( topic, ackfn )

		auxlib.assert( self == result )

		local ident = metatable:gen_ident()

		auxlib.assert( auxlib.select( 1, 2, metatable.Object:unsubscribe( ident, topic ) ) )

		metatable.Registry[ ident ] = ackfn

		return self
	end

	function metatable.__index:has_bind( topic )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_string( topic ), 2, "(string expected)" )

		return auxlib.is_function( metatable.Registry[ topic ] ) or auxlib.is_function( metatable.Strainer[ topic ] )
	end

	function metatable.__index:observe( topic, qos, proc, ackfn )

		auxlib.assert( self == result )

		self:subscribe( topic, qos, function( self, errno )

			if 0x80 ~= errno then

				auxlib.pcall( self.bind, self, topic, proc )
			end

			auxlib.pcall( ackfn, self, errno )
		end )

		return self
	end

	setmetatable( result, metatable )

	return result
end

--------------------------------------------------------------------------------

return auxmqtt
